using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Acms.SqlDal;
using System.Security.Cryptography;

namespace Acms.Bll
{
    public class User : BaseItem
    {
        public enum Genders : byte
        {
            Unknown = 0,
            Male = 1,
            Female = 2
        }

        protected User() : base() { }
        protected User(Hashtable table) : base(table) { }
        private User(Hashtable table, List<User> collection) : base(table, collection) { }
        protected User(Hashtable table, ICollection collection) : base(table, collection) { }

        protected override Base CreateDal()
        {
            return new UserDal();
        }
        
        public static User CreateInstance(int siteId)
        {
            return new User() { SiteId = siteId };
        }

        protected override void Load(Hashtable table)
        {
            Id = (int)table[UserDal.Fields.Id];
            SiteId = (int)table[UserDal.Fields.SiteId];
            UserName = (string)table[UserDal.Fields.UserName];
            FirstName = (string)table[UserDal.Fields.FirstName];
            LastName = (string)table[UserDal.Fields.LastName];
            DateOfBirth = (DateTime?)table[UserDal.Fields.DateOfBirth];
            EmailAddress = (string)table[UserDal.Fields.EmailAddress];
            EmailAuthenticated = (bool)table[UserDal.Fields.EmailAuthenticated];
            Gender = (Genders)table[UserDal.Fields.Gender];
            Address1 = (string)table[UserDal.Fields.Address1];
            Address2 = (string)table[UserDal.Fields.Address2];
            Town = (string)table[UserDal.Fields.Town];
            PostCode = (string)table[UserDal.Fields.PostCode];
            County = (string)table[UserDal.Fields.County];
            MobileTel = (string)table[UserDal.Fields.MobileTel];

            CustomBool1 = (bool?)table[UserDal.Fields.CustomBool1];
            CustomBool2 = (bool?)table[UserDal.Fields.CustomBool2];
            CustomBool3 = (bool?)table[UserDal.Fields.CustomBool3];
            CustomInt1 = (int?)table[UserDal.Fields.CustomInt1];
            CustomInt2 = (int?)table[UserDal.Fields.CustomInt2];
            CustomInt3 = (int?)table[UserDal.Fields.CustomInt3];
            CustomString1 = (string)table[UserDal.Fields.CustomString1];
            CustomString2 = (string)table[UserDal.Fields.CustomString2];
            CustomString3 = (string)table[UserDal.Fields.CustomString3];

            Created = (DateTime)table[UserDal.Fields.Created];

            Password = (string)table[UserDal.Fields.Password];
            Salt = (int)table[UserDal.Fields.Salt];
            Locked = (bool )table[UserDal.Fields.Locked];
            LastLogin = (DateTime?)table[UserDal.Fields.LastLogin];
            LastActivity = (DateTime?)table[UserDal.Fields.LastActivity];
            LastChangedPassword = (DateTime?)table[UserDal.Fields.LastChangedPassword];
            LastLocked = (DateTime?)table[UserDal.Fields.LastLocked];
        }

        public int Id { get; protected set; }
        public int SiteId { get; protected set; }
        public string UserName { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime? DateOfBirth{ get; set; }
        public string EmailAddress{ get; set; }
        public bool EmailAuthenticated { get; private set; }
        public Genders Gender{ get; set; }
	    public string Address1{ get; set; }
        public string Address2{ get; set; }
        public string Town{ get; set; }
        public string PostCode{ get; set; }
        public string County { get; set; }
        public string MobileTel{ get; set; }
        public DateTime Created{ get; protected set; }
        public bool? CustomBool1 { get; set; }
        public bool? CustomBool2 { get; set; }
        public bool? CustomBool3 { get; set; }
        public int? CustomInt1 { get; set; }
        public int? CustomInt2 { get; set; }
        public int? CustomInt3 { get; set; }
        public string CustomString1 { get; set; }
        public string CustomString2 { get; set; }
        public string CustomString3 { get; set; }
        private string Password;
        private int Salt;
        public bool Locked { get; private set; }
        public DateTime? LastLogin { get; private set; }
        public DateTime? LastActivity { get; private set; }
        public DateTime? LastChangedPassword { get; private set; }
        public DateTime? LastLocked { get; private set; }

        public string DisplayName
        {
            get
            {
                string tmp = string.Format("{0} {1}", FirstName, LastName);
                tmp = tmp.Trim();
                while (tmp.Contains("  "))
                {
                    tmp = tmp.Replace("  ", " ");
                }
                return tmp;
            }
        }

        public int Age
        {
            get { return AgeAt(DateTime.Now); }
        }

        public int AgeAt(DateTime dateTime)
        {
            int val = 0;
            if (DateOfBirth != null)
            {
                val = dateTime.Year - DateOfBirth.Value.Year;
                if (dateTime.Month < DateOfBirth.Value.Month || (dateTime.Month == DateOfBirth.Value.Month && dateTime.Day < DateOfBirth.Value.Day))
                {
                    val--;
                }
            }
            return val;
        }

        private static User Get(Hashtable table)
        {
            if (table != null)
                return new User(table);
            else
                return null;
        }

        private static List<User> Get(Hashtable[] tables)
        {
            if (tables != null)
            {
                List<User> tmp = new List<User>();
                foreach (Hashtable table in tables)
                {
                    tmp.Add(new User(table, tmp));
                }
                return tmp;
            }
            else
                return null;
        }
        
        public static User GetById(int id)
        {
            UserDal dal = new UserDal();
            return Get(dal.GetById(id));
        }

        public static User GetByUserName(string userName, int siteId)
        {
            UserDal dal = new UserDal();
            return Get(dal.GetByUserName(userName, siteId));
        }

        public static User GetByEmailAddress(string emailAddress, int siteId)
        {
            UserDal dal = new UserDal();
            return Get(dal.GetByEmailAddress(emailAddress, siteId));
        }

        public static List<User> GetAllByCustomProperty(int siteId, string name, string value)
        {
            UserDal dal = new UserDal();
            return Get(dal.GetAllByCustomProperty(siteId, name, value));
        }

        public static List<User> GetAllByRoleIdAndSiteId(int roleId, int siteId)
        {
            UserDal dal = new UserDal();
            return Get(dal.GetAllByRoleIdAndSiteId(roleId, siteId));
        }

        public static List<User> GetAllByRoleAndSiteId(Role.Roles role, int siteId)
        {
            UserDal dal = new UserDal();
            return Get(dal.GetAllByRoleIdAndSiteId((int)role, siteId));
        }

        public static List<User> GetGloabal()
        {
            UserDal dal = new UserDal();
            return Get(dal.GetGloabal());
        }

         
        internal static User GetFromLoginKey(Guid key)
        {
            UserDal dal = new UserDal();
            return Get(dal.GetFromLoginKey(key, DateTime.Now));
        }

        internal void DeleteLoginKey(Guid key)
        {
            UserDal dal = new UserDal();
            dal.DeleteLoginKey(key, this.Id);
        }

        public static List<User> Search(int siteId)
        {
            UserDal dal = new UserDal();
            return Get(dal.Search(siteId));
        }

        public static List<User> GetMostRecent(int siteId, int number)
        {
            UserDal dal = new UserDal();
            return Get(dal.GetMostRecent(siteId, number));
        }

        protected bool CustomPropertiesLoaded
        {
            get { return customProperties != null; }
        }
        private List<UserCustomProperty> customProperties;
        public List<UserCustomProperty> CustomProperties
        {
            get 
            {
                if (customProperties == null)
                {
                    customProperties = UserCustomProperty.GetAllByUserId(Id);
                }
                return customProperties;
            }
        }

        public string GetCustomProperty(string key)
        {
            if (key != null && CustomProperties != null)
            {
                foreach (UserCustomProperty p in CustomProperties)
                {
                    if (p.Name != null && p.Name.ToLower() == key.ToLower())
                    {
                        return p.Value;
                    }
                }
            }
            return null;
        }

        public void SetCustomProperty(string key, string value)
        {
            if (key != null)
            {
                if (CustomProperties == null)
                {
                    customProperties = new List<UserCustomProperty>();
                }

                bool found = false;
                foreach (UserCustomProperty p in CustomProperties)
                {
                    if (p.Name != null && p.Name.ToLower() == key.ToLower())
                    {
                        p.Value = value;
                        found = true;
                        break;
                    }
                }
                if (!found)
                {
                    customProperties.Add(UserCustomProperty.CreateInstance(Id, key ,value));
                }
            }
        }

        public List<UserRole> GetCurrentRoles(int siteId)
        {
            return UserRole.GetAllByUserIdSiteIdAndTime(Id, siteId, DateTime.Now);
        }



        public string GetFullAddress(string spacer)
        {
            string internalSpacer = "|_|";
            StringBuilder tmp = new StringBuilder();
            tmp.Append(Address1);
            tmp.Append(internalSpacer);
            tmp.Append(Address2);
            tmp.Append(internalSpacer);
            tmp.Append(Town);
            tmp.Append(internalSpacer);
            tmp.Append(PostCode);
            string tmp2 = tmp.ToString();
            while (tmp2.IndexOf(string.Concat(internalSpacer, internalSpacer)) >= 0)
            {
                tmp2 = tmp2.Replace(string.Concat(internalSpacer, internalSpacer), internalSpacer);
            }
            if (tmp2.StartsWith(internalSpacer))
            {
                tmp2 = tmp2.Substring(internalSpacer.Length);
            }
            if (tmp2.EndsWith(internalSpacer))
            {
                tmp2 = tmp2.Substring(0, tmp2.Length - internalSpacer.Length);
            }
            return tmp2.Replace(internalSpacer, spacer);
        }

        public static void Sort(List<User> users, string expression)
        {
            BaseItem.SortList(users, expression, new UserComparison());
        }


        public string GetFormattedAddress(string spacer, string postcodeSpacer)
        {
            StringBuilder tmp = new StringBuilder();
            string tmpSpacer = "";
            if(!string.IsNullOrEmpty(this.Address1))
            {
                tmp.Append(Address1);
                tmpSpacer = spacer;
            }
            if (!string.IsNullOrEmpty(this.Address2))
            {
                tmp.Append(tmpSpacer);
                tmp.Append(Address2);
                tmpSpacer = spacer;
            }
            if (!string.IsNullOrEmpty(this.Town))
            {
                tmp.Append(tmpSpacer);
                tmp.Append(Town);
                tmpSpacer = spacer;
            }
            if (!string.IsNullOrEmpty(this.County))
            {
                tmp.Append(tmpSpacer);
                tmp.Append(County);
                tmpSpacer = spacer;
            }
            if (!string.IsNullOrEmpty(this.PostCode))
            {
                //include special spacer before postcode
                if (!string.IsNullOrEmpty(tmpSpacer))
                {
                    tmp.Append(postcodeSpacer);
                }
                tmp.Append(PostCode);
            }
            return tmp.ToString();

        }

        public string ResetPassword()
        {
            CreateSalt();
            string newPassword = GenerateNewPassword();
            Password = ComputeSaltedHash(newPassword);
            return newPassword;
        }

        private string GenerateNewPassword()
        {
            Random seed = new Random();
            Random rnd = new Random(seed.Next());

            char[] alphaChars = new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
            char[] numberic = new char[] { '1', '2', '3', '4', '5', '6', '7', '8', '9' };
            char[] nonAlphNumeric = new char[] { '#', '!', '%', '*' };

            int passwordLength = 10;
            int noNonAlphNumeric = 0;
            int noNumeric = 1;

            int[] passwordFormat = new int[passwordLength];

            int noNumericSet = 0;
            while (noNumericSet < noNumeric)
            {
                int pos = rnd.Next(passwordLength);
                if (passwordFormat[pos] == 0)
                {
                    passwordFormat[pos] = 1;
                    noNumericSet++;
                }
            }

            int noNonAlphNumericSet = 0;
            while (noNonAlphNumericSet < noNonAlphNumeric)
            {
                int pos = rnd.Next(passwordLength);
                if (passwordFormat[pos] == 0)
                {
                    passwordFormat[pos] = 2;
                    noNonAlphNumericSet++;
                }
            }

            StringBuilder tmp = new StringBuilder();
            for (int i = 0; i < passwordFormat.Length; i++)
            {
                switch (passwordFormat[i])
                {
                    case 0:
                        tmp.Append(alphaChars[rnd.Next(alphaChars.Length)]);
                        break;
                    case 1:
                        tmp.Append(numberic[rnd.Next(numberic.Length)]);
                        break;
                    case 2:
                        tmp.Append(nonAlphNumeric[rnd.Next(nonAlphNumeric.Length)]);
                        break;
                }
            }
            return tmp.ToString();
        }

        public void Unlock()
        {
            Locked = false;
        }

        public void SetLogedOn()
        {
            LastLogin = DateTime.Now;
        }

        public Guid GetLoginKey(bool perminant)
        {
            Guid key = Guid.NewGuid();
            UserSecurityDal dal = new UserSecurityDal();
            dal.CreateLoginKey(key, this.Id, DateTime.Now, perminant);
            return key;
        }

        /// <summary>
        /// Sends forgotten email using email file path specified
        /// </summary>
        /// <param name="filePath"></param>
        public void SendForgottenEmail(string filePath)
        {
            string newPassword = ResetPassword();
            this.Update();
            Email.SendForgottenPasswordEmail(this, newPassword, filePath);
        }

        /// <summary>
        /// Sends forgotten email using defaul email path ~/Emails/ForgottenPassword.xml
        /// </summary>
        public void SendForgottenEmail()
        {
            string newPassword = ResetPassword();
            this.Update();
            Email.SendForgottenPasswordEmail(this, newPassword);
        }

        public new void Update()
        {
            Update(true);
        }

        public void Update(bool sendEmail)
        {
            //set created if not already set
            bool sendRegisteredEmail = false;
            if (IsNewRecord)
            {
                sendRegisteredEmail = sendEmail ? true : false;
                Created = DateTime.Now;
            }

            base.Update();

            if (sendRegisteredEmail)
            {
                try
                {
                    Email.SendRegisteredEmail(this, tmpSetPassword);
                }
                catch
                {
                    if (base.TransactionKey != Guid.Empty)
                    {
                        RollbackTransaction();
                    }
                    throw;
                }
            }
        }

        /// <summary>
        /// Sets the created and dosnt send email. Generally used for importing data progmaticvally.
        /// </summary>
        /// <param name="created"></param>
        public void Update(DateTime created)
        {
            if (IsNewRecord)
            {
                this.Created = created;
            }
            base.Update();
        }

        public new void Delete()
        {
            base.Delete();
        }

        protected override void DeleteRecord(object concurencyId, Base dal)
        {
            ((UserDal)dal).Delete(Id, concurencyId);
        }

        //used to hold password for emailing
        private string tmpSetPassword;
        public bool SetPassword(string originalPassword, string newPassword)
        {
            bool retVal = false;
            if (!string.IsNullOrEmpty(Password) && string.IsNullOrEmpty(originalPassword))
            {
                throw new ApplicationException("Original password must be supplied if password already set.");
            }
            if (string.IsNullOrEmpty(Password) || (AuthenticatePassword(originalPassword) && !AuthenticatePassword(newPassword)))
            {
                LastChangedPassword = DateTime.UtcNow;
                CreateSalt();
                Password = ComputeSaltedHash(newPassword);
                retVal = true;
            }
            tmpSetPassword = newPassword;
            return retVal;
        }

        public bool AuthenticatePassword(string password)
        {
            string tmp = ComputeSaltedHash(password);
            return tmp == this.Password;
        }

        private string ComputeSaltedHash(string password)
        {
            // Create Byte array of password string
            ASCIIEncoding encoder = new ASCIIEncoding();
            Byte[] secretBytes = encoder.GetBytes(password);

            // Create a new salt
            Byte[] saltBytes = new Byte[4];
            saltBytes[0] = (byte)(Salt >> 24);
            saltBytes[1] = (byte)(Salt >> 16);
            saltBytes[2] = (byte)(Salt >> 8);
            saltBytes[3] = (byte)(Salt);

            // append the two arrays
            Byte[] toHash = new Byte[secretBytes.Length + saltBytes.Length];
            Array.Copy(secretBytes, 0, toHash, 0, secretBytes.Length);
            Array.Copy(saltBytes, 0, toHash, secretBytes.Length, saltBytes.Length);

            SHA1 sha1 = SHA1.Create();
            Byte[] computedHash = sha1.ComputeHash(toHash);

            return encoder.GetString(computedHash);
        }

        private void CreateSalt()
        {
            Byte[] _saltBytes = new Byte[4];
            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
            rng.GetBytes(_saltBytes);

            Salt = ((((int)_saltBytes[0]) << 24) + (((int)_saltBytes[1]) << 16) + (((int)_saltBytes[2]) << 8) + ((int)_saltBytes[3]));
        }

        protected override object SaveChanges(ref object concurencyId, Base dal)
        {
            //check username unique
            User tmp = User.GetByUserName(this.UserName, this.SiteId);
            if (tmp != null && tmp.Id != this.Id)
            {
                if (this.TransactionKey != Guid.Empty)
                {
                    RollbackTransaction();
                }
                throw new Acms.Exceptions.DuplicateUsernameException("The username already belongs to another user.");

            }
            //check email unique
            tmp = User.GetByEmailAddress(this.EmailAddress, this.SiteId);
            if (tmp != null && tmp.Id != this.Id)
            {
                if (this.TransactionKey != Guid.Empty)
                {
                    RollbackTransaction();
                }
                throw new Acms.Exceptions.DuplicateEmailException("The email already belongs to another user.");
            }

            //if no transaction and saving cutom property too begin tran
            bool commitTran = false;
            if (CustomPropertiesLoaded && this.TransactionKey == Guid.Empty)
            {
                commitTran = true;
                BeginTransaction(dal);
            }

            int idOut = Id;
            ((UserDal)dal).Update(ref idOut, SiteId, UserName, FirstName, LastName, DateOfBirth, EmailAddress, EmailAuthenticated, (byte)Gender, Address1, Address2, Town, PostCode, County, MobileTel, Created, Password, Salt, CustomBool1, CustomBool2, CustomBool3, CustomInt1, CustomInt2, CustomInt3, CustomString1, CustomString2, CustomString3, Locked, LastLogin, LastActivity, LastChangedPassword, LastLocked, ref concurencyId);
            Id = idOut;

            if (CustomPropertiesLoaded)
            {
                foreach (UserCustomProperty p in CustomProperties)
                {
                    if (this.TransactionKey != Guid.Empty)
                    {
                        p.UseTransaction(this.TransactionKey);
                    }
                    p.SetUser(this);
                    p.Update();
                }
            }

            if (commitTran)
            {
                CommitTransaction(dal);
            }

            return idOut;

        }

        public class ActivateEmailAddressResult
        {
            public Results Status { get; internal set; }
            public enum Results
            {
                CodeNotFound,
                IncorrectPassword,
                Success
            }
        }

        public ActivateEmailAddressResult ActivateEmailAddress(string activateionCode, string password)
        {
            ActivateEmailAddressResult tmpResult = new ActivateEmailAddressResult();
            var tmp = EmailActivationCode.GetByUserIdAndCode(this.Id, activateionCode);
            if (tmp == null)
            {
                tmpResult.Status = ActivateEmailAddressResult.Results.CodeNotFound;
            }
            else
            {
                if (!this.AuthenticatePassword(password))
                {
                    tmpResult.Status = ActivateEmailAddressResult.Results.IncorrectPassword;
                }
                else
                {
                    this.EmailAddress = tmp.EmailAddress;
                    this.EmailAuthenticated = true;
                    tmp.Activated = DateTime.Now;
                    
                    this.BeginTransaction();
                    tmp.UseTransaction(this.TransactionKey);
                    try
                    {
                        this.Update();
                        tmp.Update();
                    }
                    catch
                    {
                        RollbackTransaction();
                        throw;
                    }
                    CommitTransaction();
                    tmpResult.Status = ActivateEmailAddressResult.Results.Success;
                }
            }
            return tmpResult;
        }


        public static User GetByEmailActivationCodeAndSiteId(string code, int siteId)
        {
            UserDal dal = new UserDal();
            return Get(dal.GetByEmailActivationCodeAndSiteId(code, siteId));
   
        }

        public static int GetCount(int siteId)
        {
            UserDal dal = new UserDal();
            return dal.GetCount(siteId);
        }
    }

    internal class UserComparison : BaseComparison<User>
    {
        internal UserComparison() : base()
        {
        }

	    public override int Compare(User x, User y)
        {
            if (x != null && y != null)
            {
                switch (Field.ToLower())
                {
                    case "id":
                        return GetReturnValue(x.Id, y.Id);
                    case "username":
                        return GetReturnValue(x.UserName, y.UserName);
                    case "firstname":
                        return GetReturnValue(x.FirstName, y.FirstName);
                    case "lastname":
                        return GetReturnValue(x.LastName, y.LastName);
                    case "displayname":
                        return GetReturnValue(x.DisplayName, y.DisplayName);
                    case "dateofbirth":
                        if (x.DateOfBirth.HasValue && y.DateOfBirth.HasValue)
                        {
                            return GetReturnValue(x.DateOfBirth.Value, y.DateOfBirth.Value);
                        }
                        else if (x.DateOfBirth.HasValue)
                        {
                            return -1;
                        }
                        else if (y.DateOfBirth.HasValue)
                        {
                            return 1;
                        }
                        else
                        {
                            return 0;
                        }
                    case "age":
                        return GetReturnValue(x.Age, y.Age);
                    case "emailaddress":
                        return GetReturnValue(x.EmailAddress, y.EmailAddress);
                    case "gender":
                        return GetReturnValue((byte)x.Gender, (byte)y.Gender);
                    case "postcode":
                        return GetReturnValue(x.PostCode, y.PostCode);
                    case "town":
                        return GetReturnValue(x.Town, y.Town);
                    case "mobiletel":
                        return GetReturnValue(x.MobileTel, y.MobileTel);
                    case "created":
                        return GetReturnValue(x.Created, y.Created);
                    default:
                        return GetReturnValue(x.GetCustomProperty(Field), y.GetCustomProperty(Field));
                }
            }
            return 0;
        }

    }
}
